﻿using System;
using System.Collections.Generic;
using System.Linq;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Web;
using MugenInjection.Web.Interface;

// ReSharper disable CheckNamespace

namespace MugenInjection.Scope
// ReSharper restore CheckNamespace
{
    /// <summary>
    /// Base lifecylce for http application, working with <see cref="IScopeProvider"/>.
    /// </summary>
    public sealed class WebContextScopeLifecycle : ScopeLifecycleBase
    {
        #region Const

        /// <summary>
        /// Session scope key.
        /// </summary>
        public const string SessionScopeProvider = "_SessionScope_";

        /// <summary>
        /// HttpRequestScope key.
        /// </summary>
        public const string HttpRequestScopeProvider = "_HttpRequestScope_";

        /// <summary>
        /// OperationRequestScope key.
        /// </summary>
        public const string OperationRequestScopeProvider = "_OperationRequest_";

        #endregion

        #region Fields

        private readonly Dictionary<object, object> _cache = new Dictionary<object, object>();
        private readonly string _scopeProviderName;
        private IScopeProvider _scopeProvider;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="WebContextScopeLifecycle"/> class.
        /// </summary>
        /// <param name="scopeProviderName"></param>
        public WebContextScopeLifecycle(string scopeProviderName)
        {
            Validate.ArgumentNotNullOrEmpty(scopeProviderName, "scopeProviderName");
            _scopeProviderName = scopeProviderName;
            if (MugenInjectionHttpApplication.IsInitialized)
                InitializeScope(MugenInjectionHttpApplication.ApplicationConfiguration);
            else
                MugenInjectionHttpApplication.ApplicationConfigurationInit += MugenInjectionHttpApplicationApplicationConfigurationInit;
        }

        #endregion

        #region Methods

        private void ValidateScope(object currentScope)
        {
            if (currentScope == null)
                throw new InvalidOperationException(string.Format(
                    "There scope provider {0} return a null scope object.", _scopeProvider.GetType()));
        }

        private void InitializeScope(IApplicationConfiguration configuration)
        {
            configuration.Injector.TryGet(out _scopeProvider, _scopeProviderName);
            if (_scopeProvider == null)
                throw new NotSupportedException(
                    string.Format(
                        @"Scope provider for {0} not found, this indicating that you not configure current HttpApplication for working with scope.",
                        _scopeProviderName));
            _scopeProvider.ReleaseScope += OnReleaseScope;
        }

        private void MugenInjectionHttpApplicationApplicationConfigurationInit(IApplicationConfiguration configuration)
        {
            MugenInjectionHttpApplication.ApplicationConfigurationInit -= MugenInjectionHttpApplicationApplicationConfigurationInit;
            InitializeScope(configuration);
        }

        private void OnReleaseScope(object scope)
        {
            ValidateScope(scope);
            lock (Locker)
            {
                if (_cache.Count == 0)
                    return;
                object service;
                if (_cache.TryGetValue(scope, out service))
                    TryReleaseObjectWithNotification(scope);
                _cache.Remove(scope);
            }
        }

        #endregion

        #region Overrides of ScopeLifecycleBase

        /// <summary>
        ///     Resolves instance for the specified <see cref="IBindingContext" /> from scope.
        /// </summary>
        /// <returns>An instance of the service.</returns>
        protected override object GetObjectFromScope()
        {
            object result;
            object currentScope = _scopeProvider.GetCurrentScope();
            ValidateScope(currentScope);
            if (!_cache.TryGetValue(currentScope, out result))
            {
                lock (Locker)
                {
                    if (!_cache.TryGetValue(currentScope, out result))
                    {
                        result = ResolveCurrentService();
                        _cache.Add(currentScope, result);
                    }
                }
            }
            return result;
        }

        /// <summary>
        ///     Gets all created objects in the current scope.
        /// </summary>
        protected override IList<object> GetObjectsFromScope()
        {
            var objects = new object[_cache.Values.Count];
            _cache.Values.CopyTo(objects, 0);
            return objects;
        }

        /// <summary>
        ///     Clears the current scope.
        /// </summary>
        protected override void ClearScope()
        {
            _cache.Clear();
        }

        /// <summary>
        ///     Releases resources held by the object.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing && !IsDisposed && _scopeProvider != null)
                _scopeProvider.ReleaseScope -= OnReleaseScope;
            base.Dispose(disposing);
        }

        #endregion
    }
}